package de.ret_world.openrpgstudio.editor;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Properties;
import org.apache.log4j.Logger;
import org.java.plugin.PluginManager;
import org.java.plugin.registry.PluginDescriptor;

/**
 * This class represents the game project in the filesystem. It holds all information about files, pathes and config files.
 *
 * @author Emu
 */
public class Project {

    private Properties config;
    private Path mainPath;
    private HashMap<String, Path> folders;
    private ScriptRegister scriptRegister;
    private MediaManager mediaManager;
    private Database database;
    private Path gameData;

    /**
     * @return the single instance of the class
     */
    public static Project getInstance() {
        if (project == null) {
            throw new RuntimeException("No active Project found! Load project first or create new one!");
        }
        return project;
    }

    /**
     * Creates a new Project at the given path.
     * @param path the path to the new project
     * @return the created project
     */
    protected static Project newProject(Path path) {
        project = new Project(path);
        project.init();
        return project;
    }

    /**
     * Loads the Project from the given path.
     * @param path the path to the project
     * @return the loaded Project object
     */
    protected static Project loadProject(Path path, PluginManager manager) {
        project = new Project(path);
        project.load(manager);
        return project;
    }

    /**
     * @return true if there is an active Project object to be loaded with "getInstance", false if there is no project loaded.
     */
    public static boolean isProjectLoaded() {
        return (project != null);
    }
    private static Project project;

    private Project(Path path) {
        folders = new HashMap<>();
        scriptRegister = new ScriptRegister();
        mediaManager = new MediaManager();
        database = new Database();

        config = new Properties();
        config.setProperty("game.version", "0.0.1");
        config.setProperty("game.author", Configuration.getInstance(Main.PLUGIN_ID).getProperty("user.name", System.getProperty("user.name")));
        mainPath = path;
        gameData = mainPath.resolve("game.data");
    }

    private void init() {
        //Generating other paths
        try {
            setFolder("root", gameData);
            setFolder("scripts", gameData.resolve("scripts"));
            setFolder("images", gameData.resolve("images"));
            setFolder("sounds", gameData.resolve("sounds"));
            setFolder("music", gameData.resolve("music"));
        } catch (IOException ex) {
            Logger.getLogger(Project.class).error(ex);
        }
    }

    private void load(PluginManager manager) {
        //loading all information from files
        try {
            config.load(Files.newInputStream(mainPath.resolve("game.rpg")));

            Properties prop = new Properties();
            prop.load(Files.newInputStream(gameData.resolve("folders.properties")));

            for (String k : prop.stringPropertyNames()) {
                folders.put(k, mainPath.resolve(prop.getProperty(k)));
            }

            scriptRegister.loadScriptRegister();
            mediaManager.loadMediaManager();

            //Plugin activation!
            for (PluginDescriptor descr : manager.getRegistry().getPluginDescriptors()) {
                String enabled = config.getProperty("plugin.enabled:" + descr.getId());
                if (enabled != null && !Boolean.valueOf(enabled)) {
                    manager.disablePlugin(descr);
                }
            }

            Logger.getLogger(Project.class).info("Project loaded successfully!");
        } catch (IOException ex) {
            Logger.getLogger(Project.class).error(ex);
        }
    }

    /**
     * @return the project configuration
     */
    public Properties getConfig() {
        return config;
    }

    /**
     * @return the path to the project root folder (not for storing asset files! use game.data-folder instead! use getFolder("root") to get it)
     */
    public Path getMainPath() {
        return mainPath;
    }

    /**
     * Returns a defined folder bound to the given key. If no folder is found the return is null. Predefined folders are "scripts" for default script folder, "images" for default images folder and "root" for the game.data folder.
     * @param key The key bound to the folder
     * @return The found folder or null
     */
    public Path getFolder(String key) {
        return folders.get(key);
    }

    /**
     * Sets a new string folder pair and creates the folder in the filesystem. Attention: If the key is already in use, the folder will be overwritten!
     * @param key The key bound to the folder
     * @param folder The folder to set
     * @throws IOException is thrown if an error occures during creating of the folder.
     */
    public void setFolder(String key, Path folder) throws IOException {
        Files.createDirectories(folder);
        folders.put(key, folder);
    }

    /**
     * Saves the Project to the file system.
     * @throws IOException if something goes wrong when saving.
     */
    public void save() throws IOException {
        Properties fold = new Properties();
        for (String k : folders.keySet()) {
            Path p = folders.get(k);
            p = mainPath.relativize(p);
            fold.put(k, p.toString());
        }

        config.store(Files.newOutputStream(mainPath.resolve("game.rpg")), "OpenRPGStudio Game Config");
        fold.store(Files.newOutputStream(gameData.resolve("folders.properties")), "OpenRPGStudio Data File");

        scriptRegister.storeScriptRegister();
        mediaManager.storeMediaManager();

        Logger.getLogger(Project.class).info("Project saved successfully!");
    }

    public ScriptRegister getScriptRegister() {
        return scriptRegister;
    }

    public MediaManager getMediaManager() {
        return mediaManager;
    }

    public Database getDatabase() {
        return database;
    }
}
